package cn.daniellee.plugin.pm.core;

import cn.daniellee.plugin.pm.PartnerMission;
import cn.daniellee.plugin.pm.menu.MissionMenu;
import cn.daniellee.plugin.pm.model.*;
import com.Zrips.CMI.CMI;
import com.Zrips.CMI.Containers.CMIUser;
import com.bekvon.bukkit.residence.Residence;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;

import java.util.*;
import java.util.stream.Collectors;

public class MissionCore {

	public static List<MissionItem> felling = new ArrayList<>();
	public static List<MissionItem> mining = new ArrayList<>();
	public static List<MissionItem> killMonster = new ArrayList<>();
	public static List<MissionItem> killAnimal = new ArrayList<>();
	public static List<MissionItem> gatherCrop = new ArrayList<>();

	public static List<RewardItem> reward = new ArrayList<>();
	public static int rewardTotalProbability = 0;

	public static List<Online> onlines = Collections.synchronizedList(new ArrayList<>());

	public static List<Invite> invites = Collections.synchronizedList(new ArrayList<>());

	public static List<Mission> missions = Collections.synchronizedList(new ArrayList<>());

	public static Map<String, Object> editOperation = new HashMap<>();

	public static void loadMissionAndReward() {
		parseMissions(PartnerMission.getInstance().getConfig().getStringList("setting.mission.felling"), felling, true);
		parseMissions(PartnerMission.getInstance().getConfig().getStringList("setting.mission.mining"), mining, true);
		parseMissions(PartnerMission.getInstance().getConfig().getStringList("setting.mission.kill-monster"), killMonster, false);
		parseMissions(PartnerMission.getInstance().getConfig().getStringList("setting.mission.kill-animal"), killAnimal, false);
		parseMissions(PartnerMission.getInstance().getConfig().getStringList("setting.mission.gather-crop"), gatherCrop, true);
		reward.clear();
		rewardTotalProbability = 0;
		PartnerMission.getInstance().getConfig().getStringList("setting.reward").forEach(item -> {
			String[] fields = item.split("\\|");
			if (fields.length != 4) {
				PartnerMission.getInstance().getLogger().info("Failed parse reward: " + item);
				return;
			}
			RewardItem rewardItem = new RewardItem();
			rewardItem.setCommand(Arrays.stream(fields[0].split(",")).toList());
			if (rewardItem.getCommand().size() == 0) {
				PartnerMission.getInstance().getLogger().info("Failed parse reward: " + item);
				return;
			}
			rewardItem.setProbability(Integer.parseInt(fields[1]));
			rewardItem.setTitle(fields[2]);
			rewardItem.setDescription(Arrays.stream(fields[3].split(";")).toList());
			reward.add(rewardItem);
			rewardTotalProbability += rewardItem.getProbability();
		});
	}

	private static void parseMissions(List<String> missions, List<MissionItem> missionItems, boolean isBlock) {
		missionItems.clear();
		missions.forEach(mission -> {
			String[] fields = mission.split("\\|");
			if (fields.length != 4) {
				PartnerMission.getInstance().getLogger().info("Failed parse mission: " + mission);
				return;
			}
			MissionItem missionItem = new MissionItem();
			List<Object> types = new ArrayList<>();
			Arrays.stream(fields[0].split(",")).toList().forEach(item -> {
				Object type = isBlock ? Material.getMaterial(item) : EntityType.valueOf(item);
				if (type != null) types.add(type);
			});
			if (types.size() == 0) {
				PartnerMission.getInstance().getLogger().info("Failed parse mission: " + mission);
				return;
			}
			missionItem.setType(types);
			missionItem.setTarget(Integer.parseInt(fields[1]));
			missionItem.setTitle(fields[2]);
			missionItem.setDescription(Arrays.stream(fields[3].split(";")).toList());
			missionItems.add(missionItem);
		});
	}

	public static PlayerData initializePlayer(String name) {
		PlayerData playerData = new PlayerData(name);
        if (PartnerMission.getInstance().hasCMI()) {
            CMIUser user = CMI.getInstance().getPlayerManager().getUser(name);
            playerData.setOnlineMinutes((int) (user.getTotalPlayTime() / 1000 / 60));
        }
		PartnerMission.getInstance().getStorage().addPlayer(playerData);
		return playerData;
	}

	public static List<PlayerData> getAndSortFilterPlayers(String exclude, boolean byTimes, boolean offline) {
		List<PlayerData> enabledPlayers = PartnerMission.getInstance().getStorage().getAllEnabledPlayer(exclude).stream().filter(item -> offline == (Bukkit.getPlayer(item.getName()) == null)).collect(Collectors.toList());
		// 冒泡排序
		for (int i = 0; i < enabledPlayers.size() - 1; i++) {
			for (int j = i + 1; j < enabledPlayers.size(); j++) {
				PlayerData a = enabledPlayers.get(i);
				PlayerData b = enabledPlayers.get(j);
				if (byTimes ? a.getFinishTimes() < b.getFinishTimes() : a.getOnlineMinutes() < b.getOnlineMinutes()) {
					enabledPlayers.set(i, b);
					enabledPlayers.set(j, a);
				}
			}
		}
		return enabledPlayers;
	}

	public static List<PlayerData> getAndSortAllPlayers(boolean byTimes) {
		List<PlayerData> enabledPlayers = PartnerMission.getInstance().getStorage().getAllEnabledPlayer("");
		// 冒泡排序
		for (int i = 0; i < enabledPlayers.size() - 1; i++) {
			for (int j = i + 1; j < enabledPlayers.size(); j++) {
				PlayerData a = enabledPlayers.get(i);
				PlayerData b = enabledPlayers.get(j);
				if (byTimes ? a.getFinishTimes() < b.getFinishTimes() : a.getOnlineMinutes() < b.getOnlineMinutes()) {
					enabledPlayers.set(i, b);
					enabledPlayers.set(j, a);
				}
			}
		}
		return enabledPlayers;
	}

	public static Mission getPlayerActiveMission(String name) {
		Mission mission = missions.stream().filter(item -> item.getInviter().equals(name) || item.getInvitee().equals(name)).findFirst().orElse(null);
		if (mission == null) return null;
		// 如果有任务但奖励已领取也返回没有任务
		if (mission.getProgress() == 5 && ((mission.getInviter().equals(name) && mission.isInviterReward()) || (mission.getInvitee().equals(name) && mission.isInviteeReward()))) return null;
		return mission;
	}

	public static String getMissionProgressLore(int missionIndex, Mission mission) {
		if (missionIndex == mission.getProgress() && mission.getProgress() < 5) {
			int current = 0;
			int target = 0;
			switch (missionIndex) {
				case 0 -> {
					current = mission.getFellingCurrent();
					target = MissionCore.felling.get(mission.getFellingIndex()).getTarget();
				}
				case 1 -> {
					current = mission.getMiningCurrent();
					target = MissionCore.mining.get(mission.getMiningIndex()).getTarget();
				}
				case 2 -> {
					current = mission.getKillMonsterCurrent();
					target = MissionCore.killMonster.get(mission.getKillMonsterIndex()).getTarget();
				}
				case 3 -> {
					current = mission.getKillAnimalCurrent();
					target = MissionCore.killAnimal.get(mission.getKillAnimalIndex()).getTarget();
				}
				case 4 -> {
					current = mission.getGatherCropCurrent();
					target = MissionCore.gatherCrop.get(mission.getGatherCropIndex()).getTarget();
				}
			}
			return PartnerMission.getInstance().getConfig().getString("menu.fields.progress.rate", "&dProgress: &r{current}/{target}").replace("{current}", Integer.toString(current)).replace("{target}", Integer.toString(target));
		} else if (missionIndex > mission.getProgress()) return PartnerMission.getInstance().getConfig().getString("menu.fields.progress.unopen", "&8&lUnopened");
		else return PartnerMission.getInstance().getConfig().getString("menu.fields.progress.complete", "&6&lComplete");
	}

	public static List<Invite> getPlayerInvitation(String name) {
		return invites.stream().filter(item -> item.getInvitee().equals(name)).toList();
	}

	public static void invitePlayer(Player inviter, Player invitee) {
		if (invitee != null && invites.stream().noneMatch(item -> item.getInviter().equals(inviter.getName()) && item.getInvitee().equals(invitee.getName()))) {
			invites.add(new Invite(inviter.getName(), invitee.getName(), System.currentTimeMillis() + PartnerMission.getInstance().getConfig().getLong("setting.invitation-period", 3) * 60 * 1000));
			inviter.sendMessage((PartnerMission.getInstance().getPrefix() + PartnerMission.getInstance().getConfig().getString("message.invite-success", "&eThe invitation is successful, valid within {period} minutes.").replace("{period}", Integer.toString(PartnerMission.getInstance().getConfig().getInt("setting.invitation-period", 3)))).replace("&", "§"));
			invitee.sendMessage((PartnerMission.getInstance().getPrefix() + PartnerMission.getInstance().getConfig().getString("message.invitee-notify", "&ePlayer &b{inviter} &einviting you to a mission, open the menu or type /pm a {inviter} to accept.").replace("{inviter}", inviter.getName())).replace("&", "§"));
		} else inviter.sendMessage((PartnerMission.getInstance().getPrefix() + PartnerMission.getInstance().getConfig().getString("message.cant-invite", "&eThe target player is not online or has been invited.")).replace("&", "§"));
	}

	public static void beginMission(Player inviter, Player invitee) {
		if (inviter != null && MissionCore.invites.removeIf(item -> item.getInviter().equals(inviter.getName()) && item.getInvitee().equals(invitee.getName()))) {
			if (MissionCore.getPlayerActiveMission(inviter.getName()) == null && MissionCore.getPlayerActiveMission(invitee.getName()) == null) {
				PlayerData inviterData = PartnerMission.getInstance().getStorage().getPlayerByName(inviter.getName());
				PlayerData inviteeData = PartnerMission.getInstance().getStorage().getPlayerByName(invitee.getName());
				long before = System.currentTimeMillis() - PartnerMission.getInstance().getConfig().getLong("setting.cooldown-period", 60) * 60 * 1000;
				if (inviterData.getLastFinish() < before && inviteeData.getLastFinish() < before) {
					Random random = new Random();
					int rewardRandom = random.nextInt(rewardTotalProbability);
					int rewardIndex = 0;
					for (int i = 0; i < reward.size(); i++) {
						rewardRandom -= reward.get(i).getProbability();
						if (rewardRandom < 0) {
							rewardIndex = i;
							break;
						}
					}
					long validUntil = System.currentTimeMillis() + PartnerMission.getInstance().getConfig().getLong("setting.execution-period", 30) * 60 * 1000;
					Mission mission = new Mission(inviter.getName(), invitee.getName(), random.nextInt(felling.size()), random.nextInt(mining.size()), random.nextInt(killMonster.size()), random.nextInt(killAnimal.size()), random.nextInt(gatherCrop.size()), rewardIndex, validUntil);
					missions.add(mission);
					sendMessageBoth(mission, (PartnerMission.getInstance().getPrefix() + PartnerMission.getInstance().getConfig().getString("message.mission-start", "&eThe mission starts, please open the menu to view the mission objective.")).replace("&", "§"));
					// 打开任务菜单
					inviter.openInventory(MissionMenu.generate(inviter, inviterData.isByTimes(), inviterData.isShowOffline(), 1));
					invitee.openInventory(MissionMenu.generate(invitee, inviteeData.isByTimes(), inviteeData.isShowOffline(), 1));
				} else {
					if (inviterData.getLastFinish() > before) inviter.sendMessage((PartnerMission.getInstance().getPrefix() + PartnerMission.getInstance().getConfig().getString("message.self-cooldown", "&eMission cooldown, remaining time: {cooldown} minutes.").replace("{cooldown}", Long.toString((inviterData.getLastFinish() - before) / 1000 / 60))).replace("&", "§"));
					if (inviteeData.getLastFinish() > before) invitee.sendMessage((PartnerMission.getInstance().getPrefix() + PartnerMission.getInstance().getConfig().getString("message.self-cooldown", "&eMission cooldown, remaining time: {cooldown} minutes.").replace("{cooldown}", Long.toString((inviterData.getLastFinish() - before) / 1000 / 60))).replace("&", "§"));
					String message = (PartnerMission.getInstance().getPrefix() + PartnerMission.getInstance().getConfig().getString("message.begin-failed", "&eOne of you is cooling down, mission start failed.")).replace("&", "§");
					inviter.sendMessage(message);
					invitee.sendMessage(message);
				}
			} else invitee.sendMessage((PartnerMission.getInstance().getPrefix() + PartnerMission.getInstance().getConfig().getString("message.on-mission", "&eSome of you are already on a mission.")).replace("&", "§"));
		} else invitee.sendMessage((PartnerMission.getInstance().getPrefix() + PartnerMission.getInstance().getConfig().getString("message.no-invite", "&eYou are not invited by this player.")).replace("&", "§"));
	}

	public static void completeMission(Mission mission) {
		PlayerData inviterData = PartnerMission.getInstance().getStorage().getPlayerByName(mission.getInviter());
		PlayerData inviteeData = PartnerMission.getInstance().getStorage().getPlayerByName(mission.getInvitee());
		long now = System.currentTimeMillis();
		inviterData.setLastFinish(now);
		inviteeData.setLastFinish(now);
		inviterData.setFinishTimes(inviterData.getFinishTimes() + 1);
		inviteeData.setFinishTimes(inviteeData.getFinishTimes() + 1);
		Map<String, Object> fields = new HashMap<>();
		fields.put("last_finish", now);
		fields.put("finish_times", inviterData.getFinishTimes());
		PartnerMission.getInstance().getStorage().updatePlayer(inviterData.getName(), fields);
		fields.put("finish_times", inviteeData.getFinishTimes());
		PartnerMission.getInstance().getStorage().updatePlayer(inviteeData.getName(), fields);
	}

	public static void sendMessageBoth(Mission mission, String message) {
		Player inviter = Bukkit.getPlayer(mission.getInviter());
		if (inviter != null) inviter.sendMessage(message);
		Player invitee = Bukkit.getPlayer(mission.getInvitee());
		if (invitee != null) invitee.sendMessage(message);
	}

	public static void sendReword(Player player, Mission mission) {
		reward.get(mission.getRewardIndex()).getCommand().forEach(item -> Bukkit.dispatchCommand(Bukkit.getConsoleSender(), item.replace("{player}", player.getName())));
		player.sendMessage((PartnerMission.getInstance().getPrefix() + PartnerMission.getInstance().getConfig().getString("message.reward-issue", "&eMission reward issued successfully.")).replace("&", "§"));
	}

	public static List<String> formatPlayerLore(List<String> playerLore, PlayerData playerData) {
		List<String> targetLore = new ArrayList<>(playerLore);
		long before = System.currentTimeMillis() - PartnerMission.getInstance().getConfig().getLong("setting.cooldown-period", 60) * 60 * 1000;
		String valid = Bukkit.getPlayer(playerData.getName()) == null ? PartnerMission.getInstance().getConfig().getString("menu.fields.valid.offline", "&cOffline") : (getPlayerActiveMission(playerData.getName()) != null ? PartnerMission.getInstance().getConfig().getString("menu.fields.valid.progress", "&6In Progress") : (playerData.getLastFinish() > before ? PartnerMission.getInstance().getConfig().getString("menu.fields.valid.cooldown", "&dCooldown {cooldown} minutes").replace("{cooldown}", Long.toString((playerData.getLastFinish() - before) / 1000 / 60)) : PartnerMission.getInstance().getConfig().getString("menu.fields.valid.waiting", "&bInviteable")));
		int introIndex = -1;
		for (int i = 0; i < targetLore.size(); i++) {
			if (targetLore.get(i).contains("{introduction}")) introIndex = i;
			targetLore.set(i, targetLore.get(i).replace("{valid}", valid).replace("{switch}", playerData.isEnabled() ? PartnerMission.getInstance().getConfig().getString("menu.fields.switch.enable", "&eEnable") : PartnerMission.getInstance().getConfig().getString("menu.fields.switch.disable", "&cDisable")).replace("{online}", PartnerMission.getInstance().getConfig().getString("menu.common.online-format", "{days}Days {hours}Hours").replace("{days}", Integer.toString(playerData.getOnlineMinutes() / 1440)).replace("{hours}", Integer.toString((playerData.getOnlineMinutes() % 1440) / 60))).replace("{times}", Integer.toString(playerData.getFinishTimes())).replace("{contact}", playerData.getContact()));
		}
		if (introIndex != -1) {
			String introFormat = targetLore.get(introIndex);
			List<String> intro = playerData.getIntroduction().stream().map(item -> introFormat.replace("{introduction}", item)).toList();
			targetLore.remove(introIndex);
			if (intro.size() == 0) targetLore.add(introIndex, introFormat.replace("{introduction}", PartnerMission.getInstance().getConfig().getString("menu.empty-intro", "None")));
			else targetLore.addAll(introIndex, intro);
		}
		return targetLore;
	}

	public static boolean checkResidencePerm(Player player, Block block) {
		if (PartnerMission.getInstance().hasResidence()) {
			return Residence.getInstance().getPlayerManager().getResidencePlayer(player).canBreakBlock(block, false);
		}
		return true;
	}
}
